
/*
 * terminal.c
 *
 * The main file of the terminal emulator
 *
 *
 * 23.05.2010, Klaus Hennemann
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "terminal.h"


static void ED (struct Betty_Terminal *t, int id);
static void CR (struct Betty_Terminal *t);

/*--------------------------------------------------------------------------*/
/*      Support functions                                                   */
/*--------------------------------------------------------------------------*/


static void move_cursor (struct Betty_Terminal *t, int x, int y)
{
	/* In origin mode, we perform y-clipping to the scrolling
	   range */
	if (t->modes.DECOM) {
		if (y < t->scroll_top)
			y = t->scroll_top;
		else if (y > t->scroll_bottom)
			y = t->scroll_bottom;
	}

	screen_cursor_goto(t->screen, x, y);
	t->wrap = 0;
}


static void move_cursor_to_home (struct Betty_Terminal *t)
{
	if (t->modes.DECOM)
		move_cursor(t, 0, t->scroll_top);
	else
		move_cursor(t, 0, 0);
}


/*--------------------------------------------------------------------------*/
/*	Terminal emulation stuff					    */
/*--------------------------------------------------------------------------*/


/* Sets the ANSI-mode MODE */
static void SM (struct Betty_Terminal *t, int mode)
{
	switch (mode) {
	case 4:
		t->modes.IRM = 1;
		break;

	case 20:
		t->modes.LNM = 1;
		break;
	}
}


/* Resets the ANSI-mode MODE */
static void RM (struct Betty_Terminal *t, int mode)
{
	switch (mode) {
	case 4:
		t->modes.IRM = 0;
		break;

	case 20:
		t->modes.LNM = 0;
		break;
	}
}


/*
 * Sets one DEC private mode specified by MODE
 */
static void DECSM (struct Betty_Terminal *t, int mode)
{
	switch (mode) {
	case 1:
		t->modes.DECCKM = 1;
		break;

	case 3:
		t->modes.DECCOLM = 1;

		if (Betty_Resize(t, 132, t->screen->height, 
				t->screen->scrollback) >= 0) 
		{
			t->scroll_top = 0;
			t->scroll_bottom = t->screen->height - 1;
			ED(t, 2);
			move_cursor_to_home(t);
		}
		break;

	case 6:
		t->modes.DECOM = 1;
		move_cursor_to_home(t);
		break;

	case 7:
		t->modes.DECAWM = 1;
		break;
	}
}


/*
 * Resets one DEC private mode specified by MODE
 */
static void DECRM (struct Betty_Terminal *t, int mode)
{
	switch (mode) {
	case 1:
		t->modes.DECCKM = 0;
		break;

	case 3:
		t->modes.DECCOLM = 0;

		if (Betty_Resize(t, 80, t->screen->height,
				t->screen->scrollback) >= 0) 
		{
			t->scroll_top = 0;
			t->scroll_bottom = t->screen->height - 1;
			ED(t, 2);
			move_cursor_to_home(t);
		}
		break;

	case 6:
		t->modes.DECOM = 0;
		move_cursor_to_home(t);
		break;

	case 7:
		t->modes.DECAWM = 0;
		break;
	}
}


/* 
 * Moves cursor up COUNT lines in same column. 
 * Cursor stops at top margin.
 */
static void CUU (struct Betty_Terminal *t, int count)
{
	if (count <= 0)
		count = 1;

	move_cursor(t, t->screen->cursor.x, t->screen->cursor.y - count);
}

/*
 * Moves cursor down COUNT lines in same column. 
 * Cursor stops at bottom margin.
 */
static void CUD (struct Betty_Terminal *t, int count)
{
	if (count <= 0)
		count = 1;

	move_cursor(t, t->screen->cursor.x, t->screen->cursor.y + count);
}


/*
 * Moves cursor right COUNT columns. 
 * Cursor stops at right margin.
 */
static void CUF (struct Betty_Terminal *t, int count)
{
	if (count <= 0)
		count = 1;

	move_cursor(t, t->screen->cursor.x + count, t->screen->cursor.y);
}


/*
 * Moves cursor left COUNT columns. 
 * Cursor stops at left margin.
 */
static void CUB (struct Betty_Terminal *t, int count)
{
	if (count <= 0)
		count = 1;

	move_cursor(t, t->screen->cursor.x - count, t->screen->cursor.y);
}


/* 
 * Moves cursor to the beginning of line and COUNT lines down.
 */
static void CNL (struct Betty_Terminal *t, int count)
{
	CR(t);
	CUD(t, count);
}


/*
 * Moves cursor to the beginning of line and COUNT lines up.
 */
static void CPL (struct Betty_Terminal *t, int count)
{
	CR(t);
	CUU(t, count);
}


/*
 * Moves the cursor to column COLUMN.
 */
static void CHA (struct Betty_Terminal *t, int column)
{
	int x;

	if (column <= 0)
		column = 1;

	/* convert to internal value */
	x = column - 1;

	move_cursor(t, x, t->screen->cursor.y);
}


/*
 * Moves cursor to line LINE, column COLUMN.
 * Origin mode (DECOM) selects line numbering and ability to move 
 * cursor into margins.
 * Beware LINE and COLUMN starts at 1!!
 */
static void CUP (struct Betty_Terminal *t, int line, int column)
{
	int x, y;

	if (line <= 0)
		line = 1;

	if (column <= 0)
		column = 1;

	x = column - 1;
	y = line - 1;

	if (t->modes.DECOM)
		y += t->scroll_top;

	move_cursor(t, x, y);
}


/*
 * Moves cursor down one line in same column. If cursor is at bottom margin, 
 * screen performs a scroll-up.
 */
static void IND (struct Betty_Terminal *t)
{
	if (t->screen->cursor.y != t->scroll_bottom) {
		CUD(t, 1);
		return;
	}

	/*
	 * If we have to scroll the whole screen, we first 
	 * have to shove the top line to the scroll back buffer. 
	 * This all is done by screen_shift_up()
	 */
	if (t->scroll_top == 0 && t->scroll_bottom == t->screen->height - 1)
		screen_shift_up(t->screen, 1);
	else
		screen_scroll_up(t->screen, t->scroll_top, 
					t->scroll_bottom, 1);
}


/*
 * Moves cursor up one line in same column. If cursor is at top margin, 
 * screen performs a scroll-down.
 */
static void RI (struct Betty_Terminal *t)
{
	if (t->screen->cursor.y == t->scroll_top)
		screen_scroll_down(t->screen, t->scroll_top, 
					t->scroll_bottom, 1);
	else
		CUU(t, 1);
}


/* Carriage return */
static void CR (struct Betty_Terminal *t)
{
	move_cursor(t, 0, t->screen->cursor.y);
}


/* 
 * Moves cursor to the next tabstop. If no tabstops are
 * configured, the cursor will be moved to the last column.
 */
static void HT (struct Betty_Terminal *t)
{
	int x;

	for (x = t->screen->cursor.x + 1; x <= t->screen->width - 1; x++)
		if (t->tabstop[x]) {
			move_cursor(t, x, t->screen->cursor.y);			
			return;
		}

	/* If no tabstop was found, we move the cursor to the last column */
	move_cursor(t, t->screen->width - 1, t->screen->cursor.y);
}


/* Sets a horitontal tab stop at current cursor column */
static void HTS (struct Betty_Terminal *t)
{
	/* the cursor position is clipped to screen size, so this
	   should be safe */
	t->tabstop[t->screen->cursor.x] = 1;
}


/* 
 * Tablulation clear. Dependant on ID the tabstop under the cursor is
 * cleared or all tab stops.
 */
static void TBC (struct Betty_Terminal *t, int id)
{
	switch (id) {
	case 0:
		t->tabstop[t->screen->cursor.x] = 0;
		break;

	case 3:
		memset(t->tabstop, 0, t->screen->width);
		break;
	}
}


/*
 * Moves cursor to first position on next line. If cursor is at bottom margin, 
 * screen performs a scroll-up.
 */
static void NEL (struct Betty_Terminal *t)
{
	IND(t);
	CR(t);
}


/*
 * Saves cursor position, character attribute (graphic rendition), 
 * character set, and origin mode selection.
 */
static void DECSC (struct Betty_Terminal *t)
{
	t->saved.wrap	= t->wrap;
	t->saved.DECOM 	= t->modes.DECOM;
	t->saved.cursor	= t->screen->cursor;
	t->saved.attr	= t->attr;
	t->saved.valid	= 1;
}


/*
 * Restores previously saved cursor position, character attribute 
 * (graphic rendition), character set, and origin mode selection. 
 * If none were saved, the cursor moves to home position.
 */
static void DECRC (struct Betty_Terminal *t)
{
	if (!t->saved.valid) {
		/* there were no values saved, so we only need to
		   set the cursor to home position */
		move_cursor_to_home(t);
		return;
	}

	/* restore values */
	t->modes.DECOM	= t->saved.DECOM;

	move_cursor(t, t->saved.cursor.x, t->saved.cursor.y);

	t->attr		= t->saved.attr;
	t->wrap		= t->saved.wrap;
}


/*
 * Selects top and bottom margins, defining the scrolling region. TOP is 
 * line number of first line in the scrolling region. BOTTOM is line number 
 * of bottom line. If TOP and BOTTOM are not selected, the complete screen 
 * is used (no margins).
 */
static void DECSTBM (struct Betty_Terminal *t, int top, int bottom)
{
	if (top <= 0)
		top = 1;

	if (bottom <= 0)
		bottom = t->screen->height;

	/* adjust to internal values */
	top--;
	bottom--;

	/* sanity check */
	if (top >= bottom || bottom >= t->screen->height)
		return;

	t->scroll_top = top;
	t->scroll_bottom = bottom;

	move_cursor_to_home(t);
}


/*
 * Select graphic rendition
 */
static void SGR (struct Betty_Terminal *t, int attr)
{
	if (attr <= 0)
		attr = 0;

	switch (attr) {
	case 0:
		t->attr = BETTY_DEFAULT_CELL_ATTR;
		break;

	case 1:
		t->attr.bold = 1;
		break;

	case 4:
		t->attr.underline = 1;
		break;

	case 5:
		t->attr.blink = 1;
		break;

	case 7:
		t->attr.reverse = 1;
		break;

	case 22:
		t->attr.bold = 0;
		break;

	case 24:
		t->attr.underline = 0;
		break;

	case 25:
		t->attr.blink = 0;
		break;

	case 27:
		t->attr.reverse = 0;
		break;

	case 39:
		t->attr.fg_palette = BETTY_PALETTE_DEFAULT;
		t->attr.fg_color_idx = BETTY_COLOR_DEFAULT_FG;
		break;

	case 49:
		t->attr.bg_palette = BETTY_PALETTE_DEFAULT;
		t->attr.bg_color_idx = BETTY_COLOR_DEFAULT_BG;
		break;

	default:
		if (attr >= 30 && attr <= 37) {
			t->attr.fg_palette = BETTY_PALETTE_XTERM;
			t->attr.fg_color_idx = attr - 30;
			break;
		}

		if (attr >= 40 && attr <= 47) {
			t->attr.bg_palette = BETTY_PALETTE_XTERM;
			t->attr.bg_color_idx = attr - 40;
			break;
		}

		if (attr >= 90 && attr <= 97) {
			t->attr.fg_palette = BETTY_PALETTE_XTERM;
			t->attr.fg_color_idx = attr - 90 + 8;
			break;
		}

		if (attr >= 100 && attr <= 107) {
			t->attr.bg_palette = BETTY_PALETTE_XTERM;
			t->attr.bg_color_idx = attr - 100 + 8;
			break;
		}
	}
}


static void print (struct Betty_Terminal *t, Betty_Character uc)
{
	/* if wrap mode is enabled and a wrap is needed and a
	   wrap is possible, perform the wrap */
	if (t->modes.DECAWM && t->wrap)
		if (t->screen->cursor.y <= t->scroll_bottom ||
		    t->screen->cursor.y < t->screen->height - 1)
			NEL(t);

	if (t->modes.IRM) {
		struct Betty_Rect r;

		r.top_left = t->screen->cursor;
		r.bottom = r.top;
		r.right = t->screen->width - 1;

		screen_scroll_right(t->screen, &r, 1);
	}

	screen_fill_active_cell(t->screen, &t->attr, uc);	

	/* Move cursor to next cell */
	if (t->screen->cursor.x < t->screen->width - 1)
		CUF(t, 1);
	else
		t->wrap = 1;
}


static void DSR (struct Betty_Terminal *t, int id)
{
        switch (id) {
        /* request a status report */
        case 5:
                /* response: Ready */
                Betty_SendString(t, "\033[0n");
                break;

        /* request cursor position */
        case 6:
                /* CPR: cursor position report */
                Betty_SendString(t, "\033[%d;%dR",
                        t->screen->cursor.y + 1 -
                                (t->modes.DECOM ? t->scroll_top : 0),
                        t->screen->cursor.x + 1);
                break;
        }
}


static void DECDSR (struct Betty_Terminal *t, int id)
{
        switch (id) {
        /* request printer status report */
        case 15:
                /* response: No printer connected */
                Betty_SendString(t, "\033[?13n"); 
                break;
        }
}


static void DA (struct Betty_Terminal *t, int id)
{
        switch (id) {
        /* request device attributes */
        case 0:
                /* response VT102 */
                Betty_SendString(t, "\033[?6c");
                break;
        }
}


/* Erase in line */
static void EL (struct Betty_Terminal *t, int id)
{
	struct Betty_Rect r;

	r.top	 = t->screen->cursor.y;
	r.bottom = t->screen->cursor.y;

	switch (id) {
	case 0:
		/* erase from cursor to end of line */
		r.left  = t->screen->cursor.x;
		r.right = t->screen->width - 1;
		screen_erase(t->screen, &r);
		break;
		
	case 1:
		/* erase from beginning of line to cursor */
		r.left  = 0;
		r.right = t->screen->cursor.x;
		screen_erase(t->screen, &r);
		break;

	case 2:
		/* erase complete line */
		r.left  = 0;
		r.right = t->screen->width - 1;
		screen_erase(t->screen, &r);
		break;
	}
}


/* erase in display */
static void ED (struct Betty_Terminal *t, int id)
{
	struct Betty_Rect r;

	switch (id) {
	case 0:
		/* erase from cursor to end of screen */
		EL(t, 0);
		r.top    = t->screen->cursor.y + 1;
		r.bottom = t->screen->height - 1;
		r.left   = 0;
		r.right  = t->screen->width - 1;
		screen_erase(t->screen, &r);
		break;

	case 1:
		/* erase from beginning of screen to cursor */
		r.top    = 0;
		r.bottom = t->screen->cursor.y - 1;
		r.left   = 0;
		r.right  = t->screen->width - 1;
		screen_erase(t->screen, &r);
		EL(t, 1);
		break;

	case 2:
		/* erase complete screen */
		r.top    = 0;
		r.bottom = t->screen->height - 1;
		r.left   = 0;
		r.right  = t->screen->width - 1;
		screen_erase(t->screen, &r);
		break;
	}
}


/*
 * Deletes COUNT characters, starting with the character at cursor position. 
 * When a character is deleted, all characters to the right of cursor move left. 
 * This creates a space character at right margin. This character has same 
 * character attribute as the last character moved left (vt102 manual).
 * 
 * In opposite to this, the vt220 manual says:
 * [...} The spaces created at the end of the line have all their character 
 * attributes off.
 *
 * Because of all tested terminal emulators use the vt220 behaviour, we do
 * the same...
 */
static void DCH (struct Betty_Terminal *t, int count)
{
	struct Betty_Rect r;

	if (count <= 0)
		count = 1;

	r.top_left = t->screen->cursor;
	r.bottom = r.top;
	r.right = t->screen->width - 1;

	screen_scroll_left(t->screen, &r, count);
}


/* 
 * Inserts COUNT empty cells starting at cursor position. The rest
 * of line is shiftet to the right
 */
static void ICH (struct Betty_Terminal *t, int count)
{
	struct Betty_Rect r;

	if (count <= 0)
		count = 1;

	r.top_left = t->screen->cursor;
	r.bottom = r.top;
	r.right = t->screen->width - 1;

	screen_scroll_right(t->screen, &r, count);
}


/*
 * Inserts COUNT lines at line with cursor. Lines displayed below cursor 
 * move down. Lines moved past the bottom margin are lost. 
 * This sequence is ignored when cursor is outside scrolling region.
 +
 * The vt220 manual says that we have to reset the cursor to first column
 * after the insert. So we do the vt220 variant.
 */
static void IL (struct Betty_Terminal *t, int count)
{
	if (t->screen->cursor.y < t->scroll_top ||
	    t->screen->cursor.y > t->scroll_bottom)
		return;

	if (count <= 0)
		count = 1;

	screen_scroll_down(t->screen, t->screen->cursor.y,
				t->scroll_bottom, count);

	CR(t);
}


/*
 * Deletes COUNT lines, starting at line with cursor. As lines are deleted, 
 * lines displayed below cursor move up. Lines added to bottom of screen have 
 * spaces with same character attributes as last line moved up. 
 * This sequence is ignored when cursor is outside scrolling region 
 * (vt102 manual).
 *
 * In opposite to this, the vt220 manual says:
 * [...] As lines are deleted, lines within the scrolling region and below 
 * the cursor move up, and blank lines are added at the bottom of the 
 * scrolling region. The cursor is reset to the first column.
 *
 * We use the vt220 behaviour.
 */
static void DL (struct Betty_Terminal *t, int count)
{
	if (t->screen->cursor.y < t->scroll_top ||
	    t->screen->cursor.y > t->scroll_bottom)
		return;

	if (count <= 0)
		count = 1;

	screen_scroll_up(t->screen, t->screen->cursor.y,
				t->scroll_bottom, count);

	CR(t);
}


/* 
 * Fills screen with uppercase E's for screen focus and alignment.
 *
 * Tests with other terminal emulators shawn, that the default
 * cell attribute is used for filling. After that the cursor is
 * moved to home position.
 */
static void DECALN (struct Betty_Terminal *t)
{
	struct Betty_Rect r;

	r.left   = 0;
	r.right  = t->screen->width - 1;
	r.top	 = 0;
	r.bottom = t->screen->height - 1;

	screen_fill(t->screen, &r, 'E', &BETTY_DEFAULT_CELL_ATTR);
	move_cursor_to_home(t);
}


/* Reset to initial state */
static void RIS (struct Betty_Terminal *t)
{
	int x;

        t->screen 		= &t->screen_normal;

	t->wrap			= 0;

	t->scroll_top		= 0;
	t->scroll_bottom	= t->screen->height - 1;

	t->modes.LNM		= 0;
	t->modes.IRM		= 0;
	t->modes.DECCOLM	= 0;
	t->modes.DECOM		= 0;
	t->modes.DECAWM		= 1;
	t->modes.DECKPAM	= 0;
	t->modes.DECCKM		= 0;

	t->attr			= BETTY_DEFAULT_CELL_ATTR;

	t->saved.valid		= 0;
	t->saved.wrap		= 0;
	t->saved.DECOM		= 0;
	t->saved.cursor.x	= 0;
	t->saved.cursor.y	= 0;
	t->saved.attr		= BETTY_DEFAULT_CELL_ATTR;

	/* set default tabstops */
	memset(t->tabstop, 0, t->screen->width);

	for (x = 8; x < t->screen->width; x += 8)
		t->tabstop[x] = 1;

	ED(t, 2);
	move_cursor_to_home(t);
}


/* DEC test functions */
static void DECTST (struct Betty_Terminal *t, int a, int b)
{
	if (a != 2)
		return;

	switch (b) {
	case 1: /* Power-up test */
		RIS(t);
		break;

	}
}


/*--------------------------------------------------------------------------*/
/*	DISPATCHER FUNCTIONS						    */
/*--------------------------------------------------------------------------*/

/*
 * Call for each parameter in D function FUNC with val set to the
 * current parameter.
 */
static void parm_iterate (struct Betty_Terminal *t, const struct ParserData *d,
			void (* func)(struct Betty_Terminal *, int val))
{
	int n;

	if (d->parm_count == 0) {
		func(t, 0);
		return;
	}

	for (n = 0; n < d->parm_count; n++)
		func(t, d->parm_value[n]);
}


/*
 * Returns the value of the parameter with index INDEX. If the parameter
 * does not exists 0 is returned.
 */
static int parm (struct ParserData *d, int index)
{
	if (index >= d->parm_count)
		return 0;

	return d->parm_value[index];
}


static inline void execute_c0c1 (struct Betty_Terminal *t, struct ParserData *d)
{
	switch (d->final) {
        case '\a': /* BEL */
		screen_bell(t->screen); 
		break;

	case '\r': /* CR */
		CR(t);	
		break;

	case '\n': /* LF/NL */
	case '\v': /* VT */
	case '\f': /* FF */
		if (t->modes.LNM)
			NEL(t);
		else
			IND(t);
		break;

	case '\t': /* HT */
		HT(t);	
		break;

	case '\b': /* BS */ 
		CUB(t, 1);
		break;
	}
}


static inline void execute_esc (struct Betty_Terminal *t, struct ParserData *d)
{
	/* ESC <CHR> */
	if (d->imed_len == 0)
		switch (d->final) {
		case '7': DECSC(t);				break;
		case '8': DECRC(t);				break;
		case '=': t->modes.DECKPAM = 1; 		break;
		case '>': t->modes.DECKPAM = 0; 		
			  t->modes.DECCKM = 0;			break;
		case 'D': IND(t);				break;
		case 'E': NEL(t);				break;
		case 'H': HTS(t);				break;
		case 'M': RI(t);				break;
                case 'Z': DA(t, 0);     			break;
		case 'c': RIS(t);				break;
		default:
			return;
		}
	/* ESC # <CHR> */
	if (strcmp(d->imed_buf, "#") == 0)
		switch (d->final) {
		case '8': DECALN(t);				break;
		default:
			return;
		}
}


static inline void execute_csi (struct Betty_Terminal *t, struct ParserData *d)
{
	/* ANSI */
	if (d->imed_len == 0)
		switch (d->final) {
		case '@': ICH(t, parm(d, 0));			break;
		case 'A': CUU(t, parm(d, 0));			break;
		case 'B': CUD(t, parm(d, 0));			break;
		case 'C': CUF(t, parm(d, 0));			break;
		case 'D': CUB(t, parm(d, 0));			break;
		case 'E': CNL(t, parm(d, 0));			break;
		case 'F': CPL(t, parm(d, 0));			break;
		case 'G': CHA(t, parm(d, 0));			break;
		case 'H': CUP(t, parm(d, 0), parm(d, 1)); 	break;
		case 'J': ED(t, parm(d, 0));			break;
		case 'K': EL(t, parm(d, 0));			break;
		case 'L': IL(t, parm(d, 0));			break;
		case 'M': DL(t, parm(d, 0));			break;
		case 'P': DCH(t, parm(d, 0));			break; 
                case 'c': parm_iterate(t, d, DA);               break;
		case 'f': CUP(t, parm(d, 0), parm(d, 1)); 	break;	/* HVP */
		case 'g': TBC(t, parm(d, 0));			break;
		case 'h': parm_iterate(t, d, SM);		break;
		case 'l': parm_iterate(t, d, RM);		break;
		case 'm': parm_iterate(t, d, SGR);		break;
                case 'n': parm_iterate(t, d, DSR);              break;
		case 'r': DECSTBM(t, parm(d, 0), parm(d, 1));	break;
		case 'y': DECTST(t, parm(d, 0), parm(d, 1));	break;
		default:
			return;
		}

	/* ANSI private */
	if (strcmp(d->imed_buf, "?") == 0)
		switch (d->final) {
		case 'h': parm_iterate(t, d, DECSM);		break;
		case 'l': parm_iterate(t, d, DECRM);		break;
                case 'n': parm_iterate(t, d, DECDSR);   	break;
		default:
			return;
		}
}


/*
 * \brief This function gets all output from the parser layer.
 *
 * \param arg   address of terminal instance
 * \param d     parser data
 */
static void terminal_execute (void *arg, struct ParserData *d)
{
        struct Betty_Terminal *t = (struct Betty_Terminal *)arg;

#ifdef DEBUG
	parser_dump("RX: ", d);
#endif

        switch (d->action) {
        case ACTION_PRINT:
		print(t, d->final);
                break;

        case ACTION_EXECUTE:
		execute_c0c1(t, d);
		break;

        case ACTION_ESC_DISPATCH:
		execute_esc(t, d);
		break;

        case ACTION_CSI_DISPATCH:
		execute_csi(t, d);
		break;

        /* external parser */
        case ACTION_HOOK:
        case ACTION_PUT:
        case ACTION_UNHOOK:
                break;

        /* operating system call */
        case ACTION_OSC_START:
        case ACTION_OSC_PUT:
        case ACTION_OSC_END:
                break;

        default:
                break;
        }
}


/*--------------------------------------------------------------------------*/
/*      library interface functions                                         */
/*--------------------------------------------------------------------------*/

struct Betty_Terminal * Betty_CreateTerminal (int width, int height,
						int scrollback)
{
        struct Betty_Terminal *t;

        if ( !(t = (struct Betty_Terminal *)malloc(sizeof(*t))) )
                return NULL;

	utf8_init(&t->utf8);

	/* init parser */
        parser_init(&t->parser, terminal_execute, t);

	/* init screens */
        if (screen_init(&t->screen_normal, width, height, scrollback) < 0 ||
            screen_init(&t->screen_alternate, width, height, 0)) {
                Betty_DestroyTerminal(t);
                return NULL;
        }

	/* allocate tabstop flag array */
	if ( !(t->tabstop = (char *)calloc(width, 1)) ) {
		Betty_DestroyTerminal(t);
		return NULL;
	}

	/* init rest of terminal */
	t->xmit			= NULL;
	t->xmit_func		= NULL;

	RIS(t);

        return t;
}


void Betty_DestroyTerminal (struct Betty_Terminal *term)
{
        parser_cleanup(&term->parser); 
        screen_cleanup(&term->screen_normal);
        screen_cleanup(&term->screen_alternate);

	if (term->tabstop)
		free(term->tabstop);

        free(term);
}


int Betty_Resize (struct Betty_Terminal *term, int width, int height, 
			int scrollback)
{
	if (width > 0 && width > term->screen->width) {
		char *tabstop;
		int x;

		if ( !(tabstop = (char *)calloc(width, 1)) )
			return -1;

		/* copy tabstop flags */
		memcpy(tabstop, term->tabstop, term->screen->width);

		/* add default tabstops */
		for (x = (term->screen->width + 7) & ~3; x < width; x += 8)
			tabstop[x] = 1;

		/* switch to new tabstop flag array */
		free(term->tabstop);
		term->tabstop = tabstop;
	}

	return screen_resize(term->screen, width, height, scrollback);
}


void Betty_SetUi (struct Betty_Terminal *term, void *ui, 
                        const struct Betty_UiOperations *ops)
{
        screen_set_ui(&term->screen_normal, ui, ops);
        screen_set_ui(&term->screen_alternate, ui, ops);
}


void Betty_SetTransmitFunction (struct Betty_Terminal *term, void *xmit,
				Betty_TransmitFunction func)
{
	term->xmit = xmit;
	term->xmit_func = func;
}


int Betty_GetWidth (struct Betty_Terminal *term)
{
	return term->screen->width;
}


int Betty_GetHeight (struct Betty_Terminal *term)
{
	return term->screen->height;
}


int Betty_GetScrollBack (struct Betty_Terminal *term)
{
	return term->screen->scrollback;
}


const struct Betty_Line * Betty_GetLine (struct Betty_Terminal *term, int y)
{
	return screen_line(term->screen, y);
}


const struct Betty_Position * Betty_GetCursorPosition (
                                struct Betty_Terminal *term)
{
	return &term->screen->cursor;
}


int Betty_ReceiveDataUTF8 (struct Betty_Terminal *term, 
				const unsigned char *buf, 
				unsigned int count)
{
	int n;

	for (n = 0; n < count; n++, buf++) {
		uint32_t uc;

		if (utf8_decode(&term->utf8, *buf, &uc) > 0)
			parser_in(&term->parser, uc);
	}

	return count;
}


int Betty_SendData (struct Betty_Terminal *term, const void *buf,
			unsigned int count)
{
	if (!term->xmit_func)
		return 0;

	return term->xmit_func(term->xmit, buf, count);
}


int Betty_SendString (struct Betty_Terminal *term, const char *format, ...)
{
        va_list ap;
        char buf[64];
        int len;

	if (!term->xmit_func)
		return 0;

        va_start(ap, format);
        len = vsnprintf(buf, sizeof(buf), format, ap);
        va_end(ap);
       
        /* output truncated */
        if (len >= sizeof(buf) || len < 0)
                return -1;

        return term->xmit_func(term->xmit, (void *)buf, len);
}



